Initial setting

Database part

Geo data and April data collections

# Select all tweets in April 2020
if(F){
paste("CREATE TABLE CoronavirusTweets",
      " AS SELECT * FROM CoronavirusTweetsCsv",
      " WHERE (strftime('%Y-%m-%d %H:%M:%S',created_at)>=",
      "strftime('%Y-%m-%d %H:%M:%S','2020-03-29 00:00:00'))",
      " AND (strftime('%Y-%m-%d %H:%M:%S',created_at)<=",
      "strftime('%Y-%m-%d %H:%M:%S','2020-04-29 23:59:59'))",sep='')%>%
  dbSendQuery(conn,.)
  
April_tweet=paste("SELECT Tweet_ID FROM CoronavirusTweets",sep='')%>%
  dbGetQuery(conn,.)
# Set Twitter developer account
create_token(app='MSSP-An-Auxiliary-Tool',
             consumer_key='ORvbA3CEOP06hi9MHfz7yknwV',
             consumer_secret='nAy2PRkiV4AYZ0NvHAF6Iw0IBFrttWMKTuxXbUWN4bcZnMpTQR',
             access_token='1328377313562509313-j1iSFuJLLo3FL768jdnHKe1fzmcWnS',
             access_secret='oJIhGoThBNSBSMLQBo3AxS5kcLrqq8sCjx6OIPX9NRmPT')
for(i in 1:ceiling(nrow(April_tweet)/90000)) {
  rl=rate_limit("lookup_statuses")
  if(rl%>%select(remaining)!=900){
    rl%>%select(reset)*60%>%ceiling()%>%Sys.sleep()
  }
  april_tweet=lookup_statuses(April_tweet$Tweet_ID[(900*i):nrow(April_tweet)])
  if(i==1){April_tweet=april_tweet}else{April_tweet=rbind(April_tweet,april_tweet)}
}
  April_tweet%>%
  select(status_id,user_id,screen_name,created_at,text,is_quote,
         is_retweet,favourites_count,retweet_count,followers_count,
         friends_count,lang)%>%
  dbWriteTable(conn,'CoronavirusTweets',.)
}
# Select all tweets with geo information from 202001 to 202011
if(F){
paste("CREATE TABLE CoronavirusTweetsGeo",
      " AS SELECT * FROM CoronavirusTweetsCsv",
      " WHERE Geolocation_coordinate='YES'",sep='')%>%
  dbSendQuery(conn,.)

Geo_tweet=paste("SELECT Tweet_ID FROM CoronavirusTweetsGeo",sep='')%>%
  dbGetQuery(conn,.)


for(i in 1:ceiling(nrow(Geo_tweet)/90000)) {
  rl=rate_limit("lookup_statuses")
  if(rl%>%select(remaining)!=900){
    rl%>%select(reset)*60%>%ceiling()%>%Sys.sleep()
  }
  geo=lookup_statuses(Geo_tweet$Tweet_ID[(900*i):nrow(Geo_tweet)])
  if(i==1){Geo=geo}else{Geo=rbind(Geo,geo)}
}

lat_lng(Geo)%>%
  select(status_id,user_id,screen_name,created_at,text,is_quote,
         is_retweet,favourites_count,retweet_count,followers_count,
         friends_count,lang,place_full_name,place_type,country_code,
         place_name,country,lat,lng)%>%
  dbWriteTable(conn,'CoronavirusTweetsGeo',.)

# Delete initial collection of covid tweets csv files table
"DROP TABLE CoronavirusTweetsCsv" %>%
  dbSendQuery(conn,.)
# Create index to accelerate query
paste("CREATE INDEX CT_status_id ON CoronavirusTweets(status_id);",
  "CREATE INDEX CTG_status_id ON CoronavirusTweetsGeo(status_id);",
  "CREATE INDEX TS_status_id ON TweetsSentiment(status_id);",
  "CREATE INDEX TGS_status_id ON TweetsGeoSentiment(status_id);",
  "CREATE INDEX CTG_lat_long ON CoronavirusTweetsGeo(lat,lng);",
  "CREATE UNIQUE INDEX GD_lat_long ON GeoDetail(lat,lng)")%>%
  dbSendQuery(conn,.)
}
dbDisconnect(conn)

Get data function

getTwitterData=function(conn,geoinfo=T,keywords=NULL,
                        period=c('2020-03-29 00:00:00','2020-04-30 23:59:59')){
  # Select table of database according to 'geoinfo'
  if(geoinfo){
    geoinfo_query=paste("SELECT CoronavirusTweetsGeo.*,",
                        "city,state,country,sentiment_score ",
                        "FROM CoronavirusTweetsGeo ",
                        "LEFT JOIN TweetsGeoSentiment ON ",
                        "CoronavirusTweetsGeo.status_id=",
                        "TweetsGeoSentiment.status_id ",
                        "LEFT JOIN GeoDetail ON ",
                        "CoronavirusTweetsGeo.lat=GeoDetail.lat ",
                        "AND CoronavirusTweetsGeo.lng=GeoDetail.lng",sep="")
  }
  else{
    geoinfo_query=paste("SELECT CoronavirusTweets.*,sentiment_score ",
                        "FROM CoronavirusTweets ",
                        "LEFT JOIN TweetsSentiment ON ",
                        "CoronavirusTweets.status_id=",
                        "TweetsSentiment.status_id",sep="")
  }
  # Add keywords conditions according to 'keywords' 
  if(length(keywords==0)){
    keywords_query=''
  }
  else{
    for(i in 1:length(keywords)){
      if(i==1){
        keywords_query=paste(" ((text LIKE '%",keywords[i],"%')",sep="")
      }
      else{
        keywords_query=keywords_query%>%
          paste("OR (text LIKE '%",keywords[i],"%')",sep="")
      }
    }
    keywords_query=paste(keywords_query,") ",sep="")
  }
  # Add period conditions according to 'period'
  if(length(period)!=2){
    period_query=''
  }
  else{
    period_query=paste(" (strftime('%Y-%m-%d %H:%M:%S',created_at)>=",
                       "strftime('%Y-%m-%d %H:%M:%S','",period[1],"') ",
                       "AND strftime('%Y-%m-%d %H:%M:%S',created_at)<=",
                       "strftime('%Y-%m-%d %H:%M:%S','",period[2],"')) ",
                       sep="")
  }
  # Write SQL
  if(period_query==''){
    if(keywords_query==''){
      query=paste(geoinfo_query,sep="")
    }
    else{
      query=paste(geoinfo_query," WHERE",keywords_query,sep="")
    }
  }
  else{
    if(keywords_query==''){
      query=paste(geoinfo_query," WHERE",period_query,sep="")
    }
    else{
      query=paste(geoinfo_query," WHERE",
                  period_query,"AND",keywords_query,sep="")
    }
  }
  # Obtain Data
 dbGetQuery(conn,query)
}

Get trend function

getTwitterTrend=function(conn,geoinfo='country',trend='day',keywords=NULL,
                       period=c('2020-03-29 00:00:00','2020-04-30 23:59:59')){
  # Add trend cconditions according to 'trend'
  if(trend=='day'){
    trend_query=c("'%Y-%m-%d'","date")
  }
  else{
    if(trend=='week'){
      trend_query=c("'%W'","week")
    }
    else{
      if(trend=='month'){
        trend_query=c("'%m'","month")
      }
      else{
        stop("The trend can only be 'day', 'week' or 'month'.") 
      }
    }
  }
    # Select table of database according to 'geoinfo'
  if(is.null(geoinfo)){
    geoinfo_query=paste("SELECT strftime(",trend_query[1],
                        ",created_at) AS ",trend_query[2],", ",
                        "count(*) AS number, ",
                        "avg(sentiment_score) AS sentiment_score ",
                        "FROM CoronavirusTweets ",
                        "LEFT JOIN TweetsSentiment ON ",
                        "CoronavirusTweets.status_id=",
                        "TweetsSentiment.status_id",sep="")
    group_query=paste(" GROUP BY strftime(",trend_query[1],
                      ",created_at)",sep="")
  }
  else{
    if(geoinfo=='country'){
    geoinfo_query=paste("SELECT strftime(",trend_query[1],
                        ",created_at) AS ",trend_query[2],", ",
                        "count(*) AS number, country, ",
                        "avg(sentiment_score) AS sentiment_score ",
                        "FROM CoronavirusTweetsGeo ",
                        "LEFT JOIN TweetsGeoSentiment ON ",
                        "CoronavirusTweetsGeo.status_id=",
                        "TweetsGeoSentiment.status_id ",
                        "LEFT JOIN GeoDetail ON ",
                        "CoronavirusTweetsGeo.lat=GeoDetail.lat ",
                        "AND CoronavirusTweetsGeo.lng=GeoDetail.lng",sep="")
    group_query=paste(" GROUP BY strftime(",trend_query[1],
                      ",created_at),country",sep="")
    }
    else{
      if(geoinfo=='state'){
        geoinfo_query=paste("SELECT strftime(",trend_query[1],
                            ",created_at) AS ",trend_query[2],", ",
                            "count(*) AS number, country, state, ",
                            "avg(sentiment_score) AS sentiment_score ",
                            "FROM CoronavirusTweetsGeo ",
                            "LEFT JOIN TweetsGeoSentiment ON ",
                            "CoronavirusTweetsGeo.status_id=",
                            "TweetsGeoSentiment.status_id ",
                            "LEFT JOIN GeoDetail ON ",
                            "CoronavirusTweetsGeo.lat=GeoDetail.lat ",
                            "AND CoronavirusTweetsGeo.lng=GeoDetail.lng",sep="")
        group_query=paste(" GROUP BY strftime(",trend_query[1],
                          ",created_at),country,state",sep="")
      }
      else{
        if(geoinfo=='city'){
          geoinfo_query=paste("SELECT strftime(",trend_query[1],
                              ",created_at) AS ",trend_query[2],", ",
                              "count(*) AS number, country, state, city, ",
                              "avg(sentiment_score) AS sentiment_score ",
                              "FROM CoronavirusTweetsGeo ",
                              "LEFT JOIN TweetsGeoSentiment ON ",
                              "CoronavirusTweetsGeo.status_id=",
                              "TweetsGeoSentiment.status_id ",
                              "LEFT JOIN GeoDetail ON ",
                              "CoronavirusTweetsGeo.lat=GeoDetail.lat ",
                              "AND CoronavirusTweetsGeo.lng=GeoDetail.lng",
                              sep="")
           group_query=paste(" GROUP BY strftime(",trend_query[1],
                             ",created_at),country,state,city",sep="")
        }
        else{
          stop("The geoinfo can only be 'NULL', 'city', 'state' or 'country'.")
        }
      }
    }
  }
  
  # Add keywords conditions according to 'keywords' 
  if(is.null(keywords)){
    keywords_query=''
  }
  else{
    for(i in 1:length(keywords)){
      if(i==1){
        keywords_query=paste(" ((text LIKE '%",keywords[i],"%')",sep="")
      }
      else{
        keywords_query=keywords_query%>%
          paste("OR (text LIKE '%",keywords[i],"%')",sep="")
      }
    }
    keywords_query=paste(keywords_query,") ",sep="")
  }
  # Add period conditions according to 'period'
  if(is.null(period)){
    period_query=''
  }
  else{
    if(length(period)==2){
          period_query=paste(" (strftime('%Y-%m-%d %H:%M:%S',created_at)>=",
                       "strftime('%Y-%m-%d %H:%M:%S','",period[1],"') ",
                       "AND strftime('%Y-%m-%d %H:%M:%S',created_at)<=",
                       "strftime('%Y-%m-%d %H:%M:%S','",period[2],"')) ",
                       sep="")
    }
    else{
      stop("The time period should be a vector with length 2.") 
    }
  }
  # Write SQL
  if(period_query==''){
    if(keywords_query==''){
      query=paste(geoinfo_query,group_query,sep="")
    }
    else{
      query=paste(geoinfo_query," WHERE",keywords_query,group_query,sep="")
    }
  }
  else{
    if(keywords_query==''){
      query=paste(geoinfo_query," WHERE",period_query,group_query,sep="")
    }
    else{
      query=paste(geoinfo_query," WHERE",period_query,"AND",keywords_query,
                  group_query,sep="")
    }
  }
  # Obtain Data
 dbGetQuery(conn,query)
}

Examples

# connect to data base
conn=dbConnect(SQLite(),dbpath)
# get twitter data with geo information
tweetsGeo=getTwitterData(conn,geoinfo = T,period = NULL)
# get twitter montly data with geo information
tweetsMonthlyGeo=getTwitterTrend(conn,geoinfo = 'country',trend='month',period=NULL)
# get twitter data with giving keywords
tweets=getTwitterData(conn,geoinfo = F,keywords = c('mask','N95','口罩'))
# get twitter daily trends giving keywords
tweetsDaily=getTwitterTrend(conn,geoinfo = NULL,keywords = c('mask','N95','口罩'))

# disconnect data base
dbDisconnect(conn)
# Release memory
rm(tweetsGeo,tweetsMonthlyGeo,tweets,tweetsDaily)
gc()

The sentiment analysis part

# For English tweets in United States
getEnScore <- function(DF, keyword){
  DFsub <- DF %>%
    filter(lang=="en"&country_code=="US")
  
  DFsub$date <- gsub("T.*", "", DFsub$created_at)
  
  wordDF <- DFsub %>%
    unnest_tokens(word, text) %>%
    anti_join(stop_words) 
  
  scoreDF <- wordDF %>%
    inner_join(get_sentiments("bing")) %>%
    count(status_id, sentiment) %>%
    spread(sentiment, n)
  scoreDF[is.na(scoreDF)] <- 0
  scoreDF <- scoreDF %>%
    mutate(sentiment_score=(positive-negative)/(positive+negative)) 
  
  tweetScoreDF <- right_join(DFsub, scoreDF, by="status_id")
  
  if(length(keyword)!=1){
    keyword <- paste(keyword, collapse="|")
  }
  keywordDF <- tweetScoreDF %>%
    filter(grepl(keyword, text))
  
  return(
    keywordDF %>%
      group_by(date) %>%
      summarize(overall_sentiment=mean(sentiment_score))
  )
}
# For all languages
# Create sentiment lexicon dictionary
# https://www.kaggle.com/rtatman/sentiment-lexicons-for-81-languages

langCode <- read.csv("SentimentLexicons/correctedMetadata.csv", header=TRUE)$`Wikipedia.Language.Code`

negTerms <- data_frame(lang=vector(), word=vector())
posTerms <- data_frame(lang=vector(), word=vector())

for(i in 1:length(langCode)){
  negTerms <- rbind(negTerms, data_frame(lang=langCode[i], word=read.delim(file=paste0("SentimentLexicons/negative_words_", langCode[i], ".txt", sep=""), header=FALSE, check.names = FALSE)))
  posTerms <- rbind(posTerms, data_frame(lang=langCode[i], word=read.delim(file=paste0("SentimentLexicons/positive_words_", langCode[i], ".txt", sep=""), header=FALSE, check.names = FALSE)))
}
negTerms$sentiment <- "negative"
posTerms$sentiment <- "positive"

mySentimentLexicon <- bind_rows(negTerms, posTerms)
mySentimentLexicon <- as.data.frame(mySentimentLexicon)

# colnames(mySentimentLexicon) <- c("lang", "word", "sentiment")
# rownames(mySentimentLexicon) <- 1:nrow(mySentimentLexicon)

# Function
getScore <- function(DF, selectedLang, keyword){
  DFsub <- DF %>%
    filter(lang==selectedLang)
  
  DFsub$date <- gsub("T.*", "", DFsub$created_at)
  
  wordDF <- DFsub %>%
    unnest_tokens(word, text) %>%
    # anti_join(fromJSON(file=paste0("stopwords-json-master/dist/", selectedLang, ".json", sep="")))
    anti_join(stopwords(language = selectedLang, source = "stopwords-iso"))
  
  scoreDF <- wordDF %>%
    inner_join(mySentimentLexicon, by=c("lang", "word")) %>%
    count(status_id, sentiment) %>%
    spread(sentiment, n)
  scoreDF[is.na(scoreDF)] <- 0
  scoreDF <- scoreDF %>%
    mutate(sentiment_score=(positive-negative)/(positive+negative)) 
  
  tweetScoreDF <- right_join(DFsub, scoreDF, by="status_id")
  
  if(length(keyword)!=1){
    keyword <- paste(keyword, collapse="|")
  }
  keywordDF <- tweetScoreDF %>%
    filter(grepl(keyword, text))
  
  return(
    keywordDF %>%
      group_by(date) %>%
      summarize(overall_sentiment=mean(sentiment_score))
  )
}

Visualization


# Firstly make a word frequency plot
# connect to data base
conn=dbConnect(SQLite(),dbpath)
# get twitter data with geo information
tweetsGeo=getTwitterData(conn,period = NULL)
# get twitter data with giving keywords and time

mask <- getTwitterTrend(conn,geoinfo = NULL,keywords = c('mask','N95'))
mask <- mutate(mask,
       x =c(1:33) )

# making a word frequent plot of mask related data.
ggplot(data = mask, aes(x = x, y = number)) +
  geom_area(color="blue",fill="purple",alpha=.2)


# making a sentiment score plot of mask related data.
ggplot(data = mask, aes(x = x, y = sentiment_score)) +
  geom_line()+
  geom_point(size=4,shape=22,color="darkred",fill="pink")

# disconnect data base
dbDisconnect(conn)

fig <- plot_ly(data, x = ~date, y = ~positiveincrease, name = 'positiveincrease', type = 'scatter', mode = 'lines+markers') 
for(i in 1:32)
  if((sentiment[i]+sentiment[i+1])/2 >0 ){
    fig <-fig %>% add_trace(y = frequency[i:(i+1)], x=date[i:(i+1)], name = NULL,  mode = 'lines',yaxis = "y2",color = I("green"))
  }else{
              
    fig <-fig %>% add_trace(y = frequency[i:(i+1)], x=date[i:(i+1)], name = NULL,  mode = 'lines',yaxis = "y2",color = I("red"))       
    }
              
fig <- fig %>% layout(
  title = "Statistics about 'mask, N95'", yaxis2 = ay,
  xaxis = list(title="x"))
fig

Geom plots


# For the geo plot
#states <- c("texas","oklahoma","kansas","louisiana","arkansas","missouri","iowa",
#"wisconsin","michigan","illinois","indiana","ohio","kentucky","tennessee",
#"alabama","mississippi","florida","georgia","south carolina","north carolina",
#"virginia","west virginia","maryland","delaware","pennsylvania","new jersey",
#"new york","connecticut","rhode island","massachusetts","vermont",
#"new hampshire","maine")

#turn data from the maps package in to a data frame suitable for plotting with ggplot2
#map_states <- map_data("county", states)
# To draw the border-by group 10
#map_states_border <- map_data("state",states)


view(tweetsGeo)
tweetsGeo <- tweetsGeo %>%
  group_by(state)%>%
  mutate(sum = n(),
         long=lng)


tweetsGeo_En <- filter(tweetsGeo, country == 'United States')
View(tweetsGeo_En)
# summary(tweetsGeo_En$sum)
# divide sum of tweets in each state into 4 parts which is 
# tweetsGeo_En$cut <- cut(tweetsGeo_En$sum,
#                      breaks=c(0,1658,7890,15064,18206),
 #                     include.lowest = T)

tweetsGeo_En_1 <- tweetsGeo_En%>%
  mutate(long=as.double(long),
         lat=as.double(lat))%>%
  group_by(state)%>%
  summarize(sum_states=n(), mean_sentiment=mean(sentiment_score))



# Make the geo plot in ggplot
#tweetsGeo_plot <- ggplot()+
#  geom_polygon(tweetsGeo_En_1, mapping=aes(x=long, y=lat, group=city), fill=cut)+
 # connects the observations in the order in which they appear in the 'map_states'
#  geom_path(map_states, mapping=aes(x=long, y=lat,group=city),color="grey")+
 #  Add the border to make it clear-by Group 10
#  geom_path(map_states_border, mapping=aes(x=long, y=lat,group=city),color="black")+
  
 #  display discrete values on a map
#  scale_fill_brewer(palette="Blues")+
  # change the name of x, y, and title
#  xlab("Longtitude")+ylab("Latitude")+ggtitle("tweetsGeo")+
 #  add marks
#  labs(fill="number of tweets")+
#  theme(plot.title = element_text(hjust = 0.5, size = 18))

# tweetsGeo_plot


Apr40 <- read.csv(file= "/Users/mac/Desktop/Trinity/us_states_daily.csv", header=TRUE)
Geoplot <- merge(Apr40,tweetsGeo_En_1 , by=c("state"))
Geoplot$hover <- with(Geoplot, paste(state, '<br>',  '<br>', "Positive:", positive, "<br>","death:", death,"<br>", "number of tweets", sum_states,'<br>', "sentiment score of this state", mean_sentiment)) #put data
fig <- plot_geo(Geoplot, locationmode = 'USA-states') 
fig <- fig %>% add_trace(
  locations = ~state,
  type='choropleth',
  z= ~mean_sentiment,
  text = ~hover,
  colors="Reds"
)
# Add a title
fig <- fig %>% layout(title = "sentiment score of each state")
# Final
fig
fig <- plot_geo(Geoplot, locationmode = 'USA-states') 
fig <- fig %>% add_trace(
  locations = ~state,
  type='choropleth',
  z= ~death,
  text = ~hover,
  colors="Purples"
)
# Add a title
fig <- fig %>% layout(title = "sentiment score of each state")
# Final
fig
LS0tCnRpdGxlOiAiQ292aWQtVHdlZXRzIgphdXRob3I6ICJHcm91cDI6RGFucGluZyBMaXUsIEhhbyBTaGVuLCBIYW9xaSBXYW5nLCBZdXhpIFdhbmciCmRhdGU6ICIyMDIwLzEyLzIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMjIEluaXRpYWwgc2V0dGluZwoqIFRoZSBkYXRhYmFzZSBjYW4gYmUgZG93bmxvYW5kIGZyb20gW09uZURyaXZlXShodHRwczovLzFkcnYubXMvdS9zIUF0b0EtUk15THBmMmhPMXJPODJwUDJ5OE9NZmctZz9lPWF6Zko0NCkKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKcGFjbWFuOjpwX2xvYWQodGlkeXZlcnNlLERCSSxSU1FMaXRlLGx1YnJpZGF0ZSxydHdlZXQsdGlkeXRleHQsbHVicmlkYXRlLHJqc29uLHBsb3RseSxwYWNtYW4sbWFwcyx0bWFwLHNwLGNhcnRvZ3JhbSxyZXZnZW8sRFQKICAgICAgICApCmRicGF0aD0iL1VzZXJzL21hYy9EZXNrdG9wL0NvdmlkLXR3ZWV0cy1lbi5kYiIKY29ubj1kYkNvbm5lY3QoU1FMaXRlKCksZGJwYXRoKQpgYGAKCiMjIERhdGFiYXNlIHBhcnQKIyMgR2VvIGRhdGEgYW5kIEFwcmlsIGRhdGEgY29sbGVjdGlvbnMKKiBOb3RlOiBUaGlzIGNodW5rIG5lZWRuJ3QgcnVuIGFnYWluLgpgYGB7cn0KIyBTZWxlY3QgYWxsIHR3ZWV0cyBpbiBBcHJpbCAyMDIwCmlmKEYpewpwYXN0ZSgiQ1JFQVRFIFRBQkxFIENvcm9uYXZpcnVzVHdlZXRzIiwKICAgICAgIiBBUyBTRUxFQ1QgKiBGUk9NIENvcm9uYXZpcnVzVHdlZXRzQ3N2IiwKICAgICAgIiBXSEVSRSAoc3RyZnRpbWUoJyVZLSVtLSVkICVIOiVNOiVTJyxjcmVhdGVkX2F0KT49IiwKICAgICAgInN0cmZ0aW1lKCclWS0lbS0lZCAlSDolTTolUycsJzIwMjAtMDMtMjkgMDA6MDA6MDAnKSkiLAogICAgICAiIEFORCAoc3RyZnRpbWUoJyVZLSVtLSVkICVIOiVNOiVTJyxjcmVhdGVkX2F0KTw9IiwKICAgICAgInN0cmZ0aW1lKCclWS0lbS0lZCAlSDolTTolUycsJzIwMjAtMDQtMjkgMjM6NTk6NTknKSkiLHNlcD0nJyklPiUKICBkYlNlbmRRdWVyeShjb25uLC4pCiAgCkFwcmlsX3R3ZWV0PXBhc3RlKCJTRUxFQ1QgVHdlZXRfSUQgRlJPTSBDb3JvbmF2aXJ1c1R3ZWV0cyIsc2VwPScnKSU+JQogIGRiR2V0UXVlcnkoY29ubiwuKQojIFNldCBUd2l0dGVyIGRldmVsb3BlciBhY2NvdW50CmNyZWF0ZV90b2tlbihhcHA9J01TU1AtQW4tQXV4aWxpYXJ5LVRvb2wnLAogICAgICAgICAgICAgY29uc3VtZXJfa2V5PSdPUnZiQTNDRU9QMDZoaTlNSGZ6N3lrbndWJywKICAgICAgICAgICAgIGNvbnN1bWVyX3NlY3JldD0nbkF5MlBSa2lWNEFZWjBOdkhBRjZJdzBJQkZydHRXTUtUdXhYYlVXTjRiY1puTXBUUVInLAogICAgICAgICAgICAgYWNjZXNzX3Rva2VuPScxMzI4Mzc3MzEzNTYyNTA5MzEzLWoxaVNGdUpMTG8zRkw3NjhqZG5IS2UxZnptY1duUycsCiAgICAgICAgICAgICBhY2Nlc3Nfc2VjcmV0PSdvSkloR29UaEJOU0JTTUxRQm8zQXhTNWtjTHJxcThzQ2p4Nk9JUFg5TlJtUFQnKQpmb3IoaSBpbiAxOmNlaWxpbmcobnJvdyhBcHJpbF90d2VldCkvOTAwMDApKSB7CiAgcmw9cmF0ZV9saW1pdCgibG9va3VwX3N0YXR1c2VzIikKICBpZihybCU+JXNlbGVjdChyZW1haW5pbmcpIT05MDApewogICAgcmwlPiVzZWxlY3QocmVzZXQpKjYwJT4lY2VpbGluZygpJT4lU3lzLnNsZWVwKCkKICB9CiAgYXByaWxfdHdlZXQ9bG9va3VwX3N0YXR1c2VzKEFwcmlsX3R3ZWV0JFR3ZWV0X0lEWyg5MDAqaSk6bnJvdyhBcHJpbF90d2VldCldKQogIGlmKGk9PTEpe0FwcmlsX3R3ZWV0PWFwcmlsX3R3ZWV0fWVsc2V7QXByaWxfdHdlZXQ9cmJpbmQoQXByaWxfdHdlZXQsYXByaWxfdHdlZXQpfQp9CiAgQXByaWxfdHdlZXQlPiUKICBzZWxlY3Qoc3RhdHVzX2lkLHVzZXJfaWQsc2NyZWVuX25hbWUsY3JlYXRlZF9hdCx0ZXh0LGlzX3F1b3RlLAogICAgICAgICBpc19yZXR3ZWV0LGZhdm91cml0ZXNfY291bnQscmV0d2VldF9jb3VudCxmb2xsb3dlcnNfY291bnQsCiAgICAgICAgIGZyaWVuZHNfY291bnQsbGFuZyklPiUKICBkYldyaXRlVGFibGUoY29ubiwnQ29yb25hdmlydXNUd2VldHMnLC4pCn0KIyBTZWxlY3QgYWxsIHR3ZWV0cyB3aXRoIGdlbyBpbmZvcm1hdGlvbiBmcm9tIDIwMjAwMSB0byAyMDIwMTEKaWYoRil7CnBhc3RlKCJDUkVBVEUgVEFCTEUgQ29yb25hdmlydXNUd2VldHNHZW8iLAogICAgICAiIEFTIFNFTEVDVCAqIEZST00gQ29yb25hdmlydXNUd2VldHNDc3YiLAogICAgICAiIFdIRVJFIEdlb2xvY2F0aW9uX2Nvb3JkaW5hdGU9J1lFUyciLHNlcD0nJyklPiUKICBkYlNlbmRRdWVyeShjb25uLC4pCgpHZW9fdHdlZXQ9cGFzdGUoIlNFTEVDVCBUd2VldF9JRCBGUk9NIENvcm9uYXZpcnVzVHdlZXRzR2VvIixzZXA9JycpJT4lCiAgZGJHZXRRdWVyeShjb25uLC4pCgoKZm9yKGkgaW4gMTpjZWlsaW5nKG5yb3coR2VvX3R3ZWV0KS85MDAwMCkpIHsKICBybD1yYXRlX2xpbWl0KCJsb29rdXBfc3RhdHVzZXMiKQogIGlmKHJsJT4lc2VsZWN0KHJlbWFpbmluZykhPTkwMCl7CiAgICBybCU+JXNlbGVjdChyZXNldCkqNjAlPiVjZWlsaW5nKCklPiVTeXMuc2xlZXAoKQogIH0KICBnZW89bG9va3VwX3N0YXR1c2VzKEdlb190d2VldCRUd2VldF9JRFsoOTAwKmkpOm5yb3coR2VvX3R3ZWV0KV0pCiAgaWYoaT09MSl7R2VvPWdlb31lbHNle0dlbz1yYmluZChHZW8sZ2VvKX0KfQoKbGF0X2xuZyhHZW8pJT4lCiAgc2VsZWN0KHN0YXR1c19pZCx1c2VyX2lkLHNjcmVlbl9uYW1lLGNyZWF0ZWRfYXQsdGV4dCxpc19xdW90ZSwKICAgICAgICAgaXNfcmV0d2VldCxmYXZvdXJpdGVzX2NvdW50LHJldHdlZXRfY291bnQsZm9sbG93ZXJzX2NvdW50LAogICAgICAgICBmcmllbmRzX2NvdW50LGxhbmcscGxhY2VfZnVsbF9uYW1lLHBsYWNlX3R5cGUsY291bnRyeV9jb2RlLAogICAgICAgICBwbGFjZV9uYW1lLGNvdW50cnksbGF0LGxuZyklPiUKICBkYldyaXRlVGFibGUoY29ubiwnQ29yb25hdmlydXNUd2VldHNHZW8nLC4pCgojIERlbGV0ZSBpbml0aWFsIGNvbGxlY3Rpb24gb2YgY292aWQgdHdlZXRzIGNzdiBmaWxlcyB0YWJsZQoiRFJPUCBUQUJMRSBDb3JvbmF2aXJ1c1R3ZWV0c0NzdiIgJT4lCiAgZGJTZW5kUXVlcnkoY29ubiwuKQojIENyZWF0ZSBpbmRleCB0byBhY2NlbGVyYXRlIHF1ZXJ5CnBhc3RlKCJDUkVBVEUgSU5ERVggQ1Rfc3RhdHVzX2lkIE9OIENvcm9uYXZpcnVzVHdlZXRzKHN0YXR1c19pZCk7IiwKICAiQ1JFQVRFIElOREVYIENUR19zdGF0dXNfaWQgT04gQ29yb25hdmlydXNUd2VldHNHZW8oc3RhdHVzX2lkKTsiLAogICJDUkVBVEUgSU5ERVggVFNfc3RhdHVzX2lkIE9OIFR3ZWV0c1NlbnRpbWVudChzdGF0dXNfaWQpOyIsCiAgIkNSRUFURSBJTkRFWCBUR1Nfc3RhdHVzX2lkIE9OIFR3ZWV0c0dlb1NlbnRpbWVudChzdGF0dXNfaWQpOyIsCiAgIkNSRUFURSBJTkRFWCBDVEdfbGF0X2xvbmcgT04gQ29yb25hdmlydXNUd2VldHNHZW8obGF0LGxuZyk7IiwKICAiQ1JFQVRFIFVOSVFVRSBJTkRFWCBHRF9sYXRfbG9uZyBPTiBHZW9EZXRhaWwobGF0LGxuZykiKSU+JQogIGRiU2VuZFF1ZXJ5KGNvbm4sLikKfQpkYkRpc2Nvbm5lY3QoY29ubikKYGBgCgojIyBHZXQgZGF0YSBmdW5jdGlvbgpgYGB7cn0KZ2V0VHdpdHRlckRhdGE9ZnVuY3Rpb24oY29ubixnZW9pbmZvPVQsa2V5d29yZHM9TlVMTCwKICAgICAgICAgICAgICAgICAgICAgICAgcGVyaW9kPWMoJzIwMjAtMDMtMjkgMDA6MDA6MDAnLCcyMDIwLTA0LTMwIDIzOjU5OjU5JykpewogICMgU2VsZWN0IHRhYmxlIG9mIGRhdGFiYXNlIGFjY29yZGluZyB0byAnZ2VvaW5mbycKICBpZihnZW9pbmZvKXsKICAgIGdlb2luZm9fcXVlcnk9cGFzdGUoIlNFTEVDVCBDb3JvbmF2aXJ1c1R3ZWV0c0dlby4qLCIsCiAgICAgICAgICAgICAgICAgICAgICAgICJjaXR5LHN0YXRlLGNvdW50cnksc2VudGltZW50X3Njb3JlICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJGUk9NIENvcm9uYXZpcnVzVHdlZXRzR2VvICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJMRUZUIEpPSU4gVHdlZXRzR2VvU2VudGltZW50IE9OICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJDb3JvbmF2aXJ1c1R3ZWV0c0dlby5zdGF0dXNfaWQ9IiwKICAgICAgICAgICAgICAgICAgICAgICAgIlR3ZWV0c0dlb1NlbnRpbWVudC5zdGF0dXNfaWQgIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkxFRlQgSk9JTiBHZW9EZXRhaWwgT04gIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkNvcm9uYXZpcnVzVHdlZXRzR2VvLmxhdD1HZW9EZXRhaWwubGF0ICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJBTkQgQ29yb25hdmlydXNUd2VldHNHZW8ubG5nPUdlb0RldGFpbC5sbmciLHNlcD0iIikKICB9CiAgZWxzZXsKICAgIGdlb2luZm9fcXVlcnk9cGFzdGUoIlNFTEVDVCBDb3JvbmF2aXJ1c1R3ZWV0cy4qLHNlbnRpbWVudF9zY29yZSAiLAogICAgICAgICAgICAgICAgICAgICAgICAiRlJPTSBDb3JvbmF2aXJ1c1R3ZWV0cyAiLAogICAgICAgICAgICAgICAgICAgICAgICAiTEVGVCBKT0lOIFR3ZWV0c1NlbnRpbWVudCBPTiAiLAogICAgICAgICAgICAgICAgICAgICAgICAiQ29yb25hdmlydXNUd2VldHMuc3RhdHVzX2lkPSIsCiAgICAgICAgICAgICAgICAgICAgICAgICJUd2VldHNTZW50aW1lbnQuc3RhdHVzX2lkIixzZXA9IiIpCiAgfQogICMgQWRkIGtleXdvcmRzIGNvbmRpdGlvbnMgYWNjb3JkaW5nIHRvICdrZXl3b3JkcycgCiAgaWYobGVuZ3RoKGtleXdvcmRzPT0wKSl7CiAgICBrZXl3b3Jkc19xdWVyeT0nJwogIH0KICBlbHNlewogICAgZm9yKGkgaW4gMTpsZW5ndGgoa2V5d29yZHMpKXsKICAgICAgaWYoaT09MSl7CiAgICAgICAga2V5d29yZHNfcXVlcnk9cGFzdGUoIiAoKHRleHQgTElLRSAnJSIsa2V5d29yZHNbaV0sIiUnKSIsc2VwPSIiKQogICAgICB9CiAgICAgIGVsc2V7CiAgICAgICAga2V5d29yZHNfcXVlcnk9a2V5d29yZHNfcXVlcnklPiUKICAgICAgICAgIHBhc3RlKCJPUiAodGV4dCBMSUtFICclIixrZXl3b3Jkc1tpXSwiJScpIixzZXA9IiIpCiAgICAgIH0KICAgIH0KICAgIGtleXdvcmRzX3F1ZXJ5PXBhc3RlKGtleXdvcmRzX3F1ZXJ5LCIpICIsc2VwPSIiKQogIH0KICAjIEFkZCBwZXJpb2QgY29uZGl0aW9ucyBhY2NvcmRpbmcgdG8gJ3BlcmlvZCcKICBpZihsZW5ndGgocGVyaW9kKSE9Mil7CiAgICBwZXJpb2RfcXVlcnk9JycKICB9CiAgZWxzZXsKICAgIHBlcmlvZF9xdWVyeT1wYXN0ZSgiIChzdHJmdGltZSgnJVktJW0tJWQgJUg6JU06JVMnLGNyZWF0ZWRfYXQpPj0iLAogICAgICAgICAgICAgICAgICAgICAgICJzdHJmdGltZSgnJVktJW0tJWQgJUg6JU06JVMnLCciLHBlcmlvZFsxXSwiJykgIiwKICAgICAgICAgICAgICAgICAgICAgICAiQU5EIHN0cmZ0aW1lKCclWS0lbS0lZCAlSDolTTolUycsY3JlYXRlZF9hdCk8PSIsCiAgICAgICAgICAgICAgICAgICAgICAgInN0cmZ0aW1lKCclWS0lbS0lZCAlSDolTTolUycsJyIscGVyaW9kWzJdLCInKSkgIiwKICAgICAgICAgICAgICAgICAgICAgICBzZXA9IiIpCiAgfQogICMgV3JpdGUgU1FMCiAgaWYocGVyaW9kX3F1ZXJ5PT0nJyl7CiAgICBpZihrZXl3b3Jkc19xdWVyeT09JycpewogICAgICBxdWVyeT1wYXN0ZShnZW9pbmZvX3F1ZXJ5LHNlcD0iIikKICAgIH0KICAgIGVsc2V7CiAgICAgIHF1ZXJ5PXBhc3RlKGdlb2luZm9fcXVlcnksIiBXSEVSRSIsa2V5d29yZHNfcXVlcnksc2VwPSIiKQogICAgfQogIH0KICBlbHNlewogICAgaWYoa2V5d29yZHNfcXVlcnk9PScnKXsKICAgICAgcXVlcnk9cGFzdGUoZ2VvaW5mb19xdWVyeSwiIFdIRVJFIixwZXJpb2RfcXVlcnksc2VwPSIiKQogICAgfQogICAgZWxzZXsKICAgICAgcXVlcnk9cGFzdGUoZ2VvaW5mb19xdWVyeSwiIFdIRVJFIiwKICAgICAgICAgICAgICAgICAgcGVyaW9kX3F1ZXJ5LCJBTkQiLGtleXdvcmRzX3F1ZXJ5LHNlcD0iIikKICAgIH0KICB9CiAgIyBPYnRhaW4gRGF0YQogZGJHZXRRdWVyeShjb25uLHF1ZXJ5KQp9CmBgYAoKCiMjIEdldCB0cmVuZCBmdW5jdGlvbgpgYGB7cn0KZ2V0VHdpdHRlclRyZW5kPWZ1bmN0aW9uKGNvbm4sZ2VvaW5mbz0nY291bnRyeScsdHJlbmQ9J2RheScsa2V5d29yZHM9TlVMTCwKICAgICAgICAgICAgICAgICAgICAgICBwZXJpb2Q9YygnMjAyMC0wMy0yOSAwMDowMDowMCcsJzIwMjAtMDQtMzAgMjM6NTk6NTknKSl7CiAgIyBBZGQgdHJlbmQgY2NvbmRpdGlvbnMgYWNjb3JkaW5nIHRvICd0cmVuZCcKICBpZih0cmVuZD09J2RheScpewogICAgdHJlbmRfcXVlcnk9YygiJyVZLSVtLSVkJyIsImRhdGUiKQogIH0KICBlbHNlewogICAgaWYodHJlbmQ9PSd3ZWVrJyl7CiAgICAgIHRyZW5kX3F1ZXJ5PWMoIiclVyciLCJ3ZWVrIikKICAgIH0KICAgIGVsc2V7CiAgICAgIGlmKHRyZW5kPT0nbW9udGgnKXsKICAgICAgICB0cmVuZF9xdWVyeT1jKCInJW0nIiwibW9udGgiKQogICAgICB9CiAgICAgIGVsc2V7CiAgICAgICAgc3RvcCgiVGhlIHRyZW5kIGNhbiBvbmx5IGJlICdkYXknLCAnd2Vlaycgb3IgJ21vbnRoJy4iKSAKICAgICAgfQogICAgfQogIH0KICAgICMgU2VsZWN0IHRhYmxlIG9mIGRhdGFiYXNlIGFjY29yZGluZyB0byAnZ2VvaW5mbycKICBpZihpcy5udWxsKGdlb2luZm8pKXsKICAgIGdlb2luZm9fcXVlcnk9cGFzdGUoIlNFTEVDVCBzdHJmdGltZSgiLHRyZW5kX3F1ZXJ5WzFdLAogICAgICAgICAgICAgICAgICAgICAgICAiLGNyZWF0ZWRfYXQpIEFTICIsdHJlbmRfcXVlcnlbMl0sIiwgIiwKICAgICAgICAgICAgICAgICAgICAgICAgImNvdW50KCopIEFTIG51bWJlciwgIiwKICAgICAgICAgICAgICAgICAgICAgICAgImF2ZyhzZW50aW1lbnRfc2NvcmUpIEFTIHNlbnRpbWVudF9zY29yZSAiLAogICAgICAgICAgICAgICAgICAgICAgICAiRlJPTSBDb3JvbmF2aXJ1c1R3ZWV0cyAiLAogICAgICAgICAgICAgICAgICAgICAgICAiTEVGVCBKT0lOIFR3ZWV0c1NlbnRpbWVudCBPTiAiLAogICAgICAgICAgICAgICAgICAgICAgICAiQ29yb25hdmlydXNUd2VldHMuc3RhdHVzX2lkPSIsCiAgICAgICAgICAgICAgICAgICAgICAgICJUd2VldHNTZW50aW1lbnQuc3RhdHVzX2lkIixzZXA9IiIpCiAgICBncm91cF9xdWVyeT1wYXN0ZSgiIEdST1VQIEJZIHN0cmZ0aW1lKCIsdHJlbmRfcXVlcnlbMV0sCiAgICAgICAgICAgICAgICAgICAgICAiLGNyZWF0ZWRfYXQpIixzZXA9IiIpCiAgfQogIGVsc2V7CiAgICBpZihnZW9pbmZvPT0nY291bnRyeScpewogICAgZ2VvaW5mb19xdWVyeT1wYXN0ZSgiU0VMRUNUIHN0cmZ0aW1lKCIsdHJlbmRfcXVlcnlbMV0sCiAgICAgICAgICAgICAgICAgICAgICAgICIsY3JlYXRlZF9hdCkgQVMgIix0cmVuZF9xdWVyeVsyXSwiLCAiLAogICAgICAgICAgICAgICAgICAgICAgICAiY291bnQoKikgQVMgbnVtYmVyLCBjb3VudHJ5LCAiLAogICAgICAgICAgICAgICAgICAgICAgICAiYXZnKHNlbnRpbWVudF9zY29yZSkgQVMgc2VudGltZW50X3Njb3JlICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJGUk9NIENvcm9uYXZpcnVzVHdlZXRzR2VvICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJMRUZUIEpPSU4gVHdlZXRzR2VvU2VudGltZW50IE9OICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJDb3JvbmF2aXJ1c1R3ZWV0c0dlby5zdGF0dXNfaWQ9IiwKICAgICAgICAgICAgICAgICAgICAgICAgIlR3ZWV0c0dlb1NlbnRpbWVudC5zdGF0dXNfaWQgIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkxFRlQgSk9JTiBHZW9EZXRhaWwgT04gIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkNvcm9uYXZpcnVzVHdlZXRzR2VvLmxhdD1HZW9EZXRhaWwubGF0ICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJBTkQgQ29yb25hdmlydXNUd2VldHNHZW8ubG5nPUdlb0RldGFpbC5sbmciLHNlcD0iIikKICAgIGdyb3VwX3F1ZXJ5PXBhc3RlKCIgR1JPVVAgQlkgc3RyZnRpbWUoIix0cmVuZF9xdWVyeVsxXSwKICAgICAgICAgICAgICAgICAgICAgICIsY3JlYXRlZF9hdCksY291bnRyeSIsc2VwPSIiKQogICAgfQogICAgZWxzZXsKICAgICAgaWYoZ2VvaW5mbz09J3N0YXRlJyl7CiAgICAgICAgZ2VvaW5mb19xdWVyeT1wYXN0ZSgiU0VMRUNUIHN0cmZ0aW1lKCIsdHJlbmRfcXVlcnlbMV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiLGNyZWF0ZWRfYXQpIEFTICIsdHJlbmRfcXVlcnlbMl0sIiwgIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjb3VudCgqKSBBUyBudW1iZXIsIGNvdW50cnksIHN0YXRlLCAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImF2ZyhzZW50aW1lbnRfc2NvcmUpIEFTIHNlbnRpbWVudF9zY29yZSAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIkZST00gQ29yb25hdmlydXNUd2VldHNHZW8gIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJMRUZUIEpPSU4gVHdlZXRzR2VvU2VudGltZW50IE9OICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ29yb25hdmlydXNUd2VldHNHZW8uc3RhdHVzX2lkPSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVHdlZXRzR2VvU2VudGltZW50LnN0YXR1c19pZCAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIkxFRlQgSk9JTiBHZW9EZXRhaWwgT04gIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDb3JvbmF2aXJ1c1R3ZWV0c0dlby5sYXQ9R2VvRGV0YWlsLmxhdCAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFORCBDb3JvbmF2aXJ1c1R3ZWV0c0dlby5sbmc9R2VvRGV0YWlsLmxuZyIsc2VwPSIiKQogICAgICAgIGdyb3VwX3F1ZXJ5PXBhc3RlKCIgR1JPVVAgQlkgc3RyZnRpbWUoIix0cmVuZF9xdWVyeVsxXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAiLGNyZWF0ZWRfYXQpLGNvdW50cnksc3RhdGUiLHNlcD0iIikKICAgICAgfQogICAgICBlbHNlewogICAgICAgIGlmKGdlb2luZm89PSdjaXR5Jyl7CiAgICAgICAgICBnZW9pbmZvX3F1ZXJ5PXBhc3RlKCJTRUxFQ1Qgc3RyZnRpbWUoIix0cmVuZF9xdWVyeVsxXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIixjcmVhdGVkX2F0KSBBUyAiLHRyZW5kX3F1ZXJ5WzJdLCIsICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjb3VudCgqKSBBUyBudW1iZXIsIGNvdW50cnksIHN0YXRlLCBjaXR5LCAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYXZnKHNlbnRpbWVudF9zY29yZSkgQVMgc2VudGltZW50X3Njb3JlICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJGUk9NIENvcm9uYXZpcnVzVHdlZXRzR2VvICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJMRUZUIEpPSU4gVHdlZXRzR2VvU2VudGltZW50IE9OICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDb3JvbmF2aXJ1c1R3ZWV0c0dlby5zdGF0dXNfaWQ9IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlR3ZWV0c0dlb1NlbnRpbWVudC5zdGF0dXNfaWQgIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkxFRlQgSk9JTiBHZW9EZXRhaWwgT04gIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNvcm9uYXZpcnVzVHdlZXRzR2VvLmxhdD1HZW9EZXRhaWwubGF0ICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBTkQgQ29yb25hdmlydXNUd2VldHNHZW8ubG5nPUdlb0RldGFpbC5sbmciLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXA9IiIpCiAgICAgICAgICAgZ3JvdXBfcXVlcnk9cGFzdGUoIiBHUk9VUCBCWSBzdHJmdGltZSgiLHRyZW5kX3F1ZXJ5WzFdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICIsY3JlYXRlZF9hdCksY291bnRyeSxzdGF0ZSxjaXR5IixzZXA9IiIpCiAgICAgICAgfQogICAgICAgIGVsc2V7CiAgICAgICAgICBzdG9wKCJUaGUgZ2VvaW5mbyBjYW4gb25seSBiZSAnTlVMTCcsICdjaXR5JywgJ3N0YXRlJyBvciAnY291bnRyeScuIikKICAgICAgICB9CiAgICAgIH0KICAgIH0KICB9CiAgCiAgIyBBZGQga2V5d29yZHMgY29uZGl0aW9ucyBhY2NvcmRpbmcgdG8gJ2tleXdvcmRzJyAKICBpZihpcy5udWxsKGtleXdvcmRzKSl7CiAgICBrZXl3b3Jkc19xdWVyeT0nJwogIH0KICBlbHNlewogICAgZm9yKGkgaW4gMTpsZW5ndGgoa2V5d29yZHMpKXsKICAgICAgaWYoaT09MSl7CiAgICAgICAga2V5d29yZHNfcXVlcnk9cGFzdGUoIiAoKHRleHQgTElLRSAnJSIsa2V5d29yZHNbaV0sIiUnKSIsc2VwPSIiKQogICAgICB9CiAgICAgIGVsc2V7CiAgICAgICAga2V5d29yZHNfcXVlcnk9a2V5d29yZHNfcXVlcnklPiUKICAgICAgICAgIHBhc3RlKCJPUiAodGV4dCBMSUtFICclIixrZXl3b3Jkc1tpXSwiJScpIixzZXA9IiIpCiAgICAgIH0KICAgIH0KICAgIGtleXdvcmRzX3F1ZXJ5PXBhc3RlKGtleXdvcmRzX3F1ZXJ5LCIpICIsc2VwPSIiKQogIH0KICAjIEFkZCBwZXJpb2QgY29uZGl0aW9ucyBhY2NvcmRpbmcgdG8gJ3BlcmlvZCcKICBpZihpcy5udWxsKHBlcmlvZCkpewogICAgcGVyaW9kX3F1ZXJ5PScnCiAgfQogIGVsc2V7CiAgICBpZihsZW5ndGgocGVyaW9kKT09Mil7CiAgICAgICAgICBwZXJpb2RfcXVlcnk9cGFzdGUoIiAoc3RyZnRpbWUoJyVZLSVtLSVkICVIOiVNOiVTJyxjcmVhdGVkX2F0KT49IiwKICAgICAgICAgICAgICAgICAgICAgICAic3RyZnRpbWUoJyVZLSVtLSVkICVIOiVNOiVTJywnIixwZXJpb2RbMV0sIicpICIsCiAgICAgICAgICAgICAgICAgICAgICAgIkFORCBzdHJmdGltZSgnJVktJW0tJWQgJUg6JU06JVMnLGNyZWF0ZWRfYXQpPD0iLAogICAgICAgICAgICAgICAgICAgICAgICJzdHJmdGltZSgnJVktJW0tJWQgJUg6JU06JVMnLCciLHBlcmlvZFsyXSwiJykpICIsCiAgICAgICAgICAgICAgICAgICAgICAgc2VwPSIiKQogICAgfQogICAgZWxzZXsKICAgICAgc3RvcCgiVGhlIHRpbWUgcGVyaW9kIHNob3VsZCBiZSBhIHZlY3RvciB3aXRoIGxlbmd0aCAyLiIpIAogICAgfQogIH0KICAjIFdyaXRlIFNRTAogIGlmKHBlcmlvZF9xdWVyeT09JycpewogICAgaWYoa2V5d29yZHNfcXVlcnk9PScnKXsKICAgICAgcXVlcnk9cGFzdGUoZ2VvaW5mb19xdWVyeSxncm91cF9xdWVyeSxzZXA9IiIpCiAgICB9CiAgICBlbHNlewogICAgICBxdWVyeT1wYXN0ZShnZW9pbmZvX3F1ZXJ5LCIgV0hFUkUiLGtleXdvcmRzX3F1ZXJ5LGdyb3VwX3F1ZXJ5LHNlcD0iIikKICAgIH0KICB9CiAgZWxzZXsKICAgIGlmKGtleXdvcmRzX3F1ZXJ5PT0nJyl7CiAgICAgIHF1ZXJ5PXBhc3RlKGdlb2luZm9fcXVlcnksIiBXSEVSRSIscGVyaW9kX3F1ZXJ5LGdyb3VwX3F1ZXJ5LHNlcD0iIikKICAgIH0KICAgIGVsc2V7CiAgICAgIHF1ZXJ5PXBhc3RlKGdlb2luZm9fcXVlcnksIiBXSEVSRSIscGVyaW9kX3F1ZXJ5LCJBTkQiLGtleXdvcmRzX3F1ZXJ5LAogICAgICAgICAgICAgICAgICBncm91cF9xdWVyeSxzZXA9IiIpCiAgICB9CiAgfQogICMgT2J0YWluIERhdGEKIGRiR2V0UXVlcnkoY29ubixxdWVyeSkKfQpgYGAKCgojIyBFeGFtcGxlcwpgYGB7cn0KIyBjb25uZWN0IHRvIGRhdGEgYmFzZQpjb25uPWRiQ29ubmVjdChTUUxpdGUoKSxkYnBhdGgpCiMgZ2V0IHR3aXR0ZXIgZGF0YSB3aXRoIGdlbyBpbmZvcm1hdGlvbgp0d2VldHNHZW89Z2V0VHdpdHRlckRhdGEoY29ubixnZW9pbmZvID0gVCxwZXJpb2QgPSBOVUxMKQojIGdldCB0d2l0dGVyIG1vbnRseSBkYXRhIHdpdGggZ2VvIGluZm9ybWF0aW9uCnR3ZWV0c01vbnRobHlHZW89Z2V0VHdpdHRlclRyZW5kKGNvbm4sZ2VvaW5mbyA9ICdjb3VudHJ5Jyx0cmVuZD0nbW9udGgnLHBlcmlvZD1OVUxMKQojIGdldCB0d2l0dGVyIGRhdGEgd2l0aCBnaXZpbmcga2V5d29yZHMKdHdlZXRzPWdldFR3aXR0ZXJEYXRhKGNvbm4sZ2VvaW5mbyA9IEYsa2V5d29yZHMgPSBjKCdtYXNrJywnTjk1Jywn5Y+j572pJykpCiMgZ2V0IHR3aXR0ZXIgZGFpbHkgdHJlbmRzIGdpdmluZyBrZXl3b3Jkcwp0d2VldHNEYWlseT1nZXRUd2l0dGVyVHJlbmQoY29ubixnZW9pbmZvID0gTlVMTCxrZXl3b3JkcyA9IGMoJ21hc2snLCdOOTUnLCflj6PnvaknKSkKCiMgZGlzY29ubmVjdCBkYXRhIGJhc2UKZGJEaXNjb25uZWN0KGNvbm4pCiMgUmVsZWFzZSBtZW1vcnkKcm0odHdlZXRzR2VvLHR3ZWV0c01vbnRobHlHZW8sdHdlZXRzLHR3ZWV0c0RhaWx5KQpnYygpCmBgYAoKIyBUaGUgc2VudGltZW50IGFuYWx5c2lzIHBhcnQKYGBge3J9CiMgRm9yIEVuZ2xpc2ggdHdlZXRzIGluIFVuaXRlZCBTdGF0ZXMKZ2V0RW5TY29yZSA8LSBmdW5jdGlvbihERiwga2V5d29yZCl7CiAgREZzdWIgPC0gREYgJT4lCiAgICBmaWx0ZXIobGFuZz09ImVuIiZjb3VudHJ5X2NvZGU9PSJVUyIpCiAgCiAgREZzdWIkZGF0ZSA8LSBnc3ViKCJULioiLCAiIiwgREZzdWIkY3JlYXRlZF9hdCkKICAKICB3b3JkREYgPC0gREZzdWIgJT4lCiAgICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQogICAgYW50aV9qb2luKHN0b3Bfd29yZHMpIAogIAogIHNjb3JlREYgPC0gd29yZERGICU+JQogICAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUKICAgIGNvdW50KHN0YXR1c19pZCwgc2VudGltZW50KSAlPiUKICAgIHNwcmVhZChzZW50aW1lbnQsIG4pCiAgc2NvcmVERltpcy5uYShzY29yZURGKV0gPC0gMAogIHNjb3JlREYgPC0gc2NvcmVERiAlPiUKICAgIG11dGF0ZShzZW50aW1lbnRfc2NvcmU9KHBvc2l0aXZlLW5lZ2F0aXZlKS8ocG9zaXRpdmUrbmVnYXRpdmUpKSAKICAKICB0d2VldFNjb3JlREYgPC0gcmlnaHRfam9pbihERnN1Yiwgc2NvcmVERiwgYnk9InN0YXR1c19pZCIpCiAgCiAgaWYobGVuZ3RoKGtleXdvcmQpIT0xKXsKICAgIGtleXdvcmQgPC0gcGFzdGUoa2V5d29yZCwgY29sbGFwc2U9InwiKQogIH0KICBrZXl3b3JkREYgPC0gdHdlZXRTY29yZURGICU+JQogICAgZmlsdGVyKGdyZXBsKGtleXdvcmQsIHRleHQpKQogIAogIHJldHVybigKICAgIGtleXdvcmRERiAlPiUKICAgICAgZ3JvdXBfYnkoZGF0ZSkgJT4lCiAgICAgIHN1bW1hcml6ZShvdmVyYWxsX3NlbnRpbWVudD1tZWFuKHNlbnRpbWVudF9zY29yZSkpCiAgKQp9CgpgYGAKCmBgYHtyfQojIEZvciBhbGwgbGFuZ3VhZ2VzCiMgQ3JlYXRlIHNlbnRpbWVudCBsZXhpY29uIGRpY3Rpb25hcnkKIyBodHRwczovL3d3dy5rYWdnbGUuY29tL3J0YXRtYW4vc2VudGltZW50LWxleGljb25zLWZvci04MS1sYW5ndWFnZXMKCmxhbmdDb2RlIDwtIHJlYWQuY3N2KCJTZW50aW1lbnRMZXhpY29ucy9jb3JyZWN0ZWRNZXRhZGF0YS5jc3YiLCBoZWFkZXI9VFJVRSkkYFdpa2lwZWRpYS5MYW5ndWFnZS5Db2RlYAoKbmVnVGVybXMgPC0gZGF0YV9mcmFtZShsYW5nPXZlY3RvcigpLCB3b3JkPXZlY3RvcigpKQpwb3NUZXJtcyA8LSBkYXRhX2ZyYW1lKGxhbmc9dmVjdG9yKCksIHdvcmQ9dmVjdG9yKCkpCgpmb3IoaSBpbiAxOmxlbmd0aChsYW5nQ29kZSkpewogIG5lZ1Rlcm1zIDwtIHJiaW5kKG5lZ1Rlcm1zLCBkYXRhX2ZyYW1lKGxhbmc9bGFuZ0NvZGVbaV0sIHdvcmQ9cmVhZC5kZWxpbShmaWxlPXBhc3RlMCgiU2VudGltZW50TGV4aWNvbnMvbmVnYXRpdmVfd29yZHNfIiwgbGFuZ0NvZGVbaV0sICIudHh0Iiwgc2VwPSIiKSwgaGVhZGVyPUZBTFNFLCBjaGVjay5uYW1lcyA9IEZBTFNFKSkpCiAgcG9zVGVybXMgPC0gcmJpbmQocG9zVGVybXMsIGRhdGFfZnJhbWUobGFuZz1sYW5nQ29kZVtpXSwgd29yZD1yZWFkLmRlbGltKGZpbGU9cGFzdGUwKCJTZW50aW1lbnRMZXhpY29ucy9wb3NpdGl2ZV93b3Jkc18iLCBsYW5nQ29kZVtpXSwgIi50eHQiLCBzZXA9IiIpLCBoZWFkZXI9RkFMU0UsIGNoZWNrLm5hbWVzID0gRkFMU0UpKSkKfQpuZWdUZXJtcyRzZW50aW1lbnQgPC0gIm5lZ2F0aXZlIgpwb3NUZXJtcyRzZW50aW1lbnQgPC0gInBvc2l0aXZlIgoKbXlTZW50aW1lbnRMZXhpY29uIDwtIGJpbmRfcm93cyhuZWdUZXJtcywgcG9zVGVybXMpCm15U2VudGltZW50TGV4aWNvbiA8LSBhcy5kYXRhLmZyYW1lKG15U2VudGltZW50TGV4aWNvbikKCiMgY29sbmFtZXMobXlTZW50aW1lbnRMZXhpY29uKSA8LSBjKCJsYW5nIiwgIndvcmQiLCAic2VudGltZW50IikKIyByb3duYW1lcyhteVNlbnRpbWVudExleGljb24pIDwtIDE6bnJvdyhteVNlbnRpbWVudExleGljb24pCgojIEZ1bmN0aW9uCmdldFNjb3JlIDwtIGZ1bmN0aW9uKERGLCBzZWxlY3RlZExhbmcsIGtleXdvcmQpewogIERGc3ViIDwtIERGICU+JQogICAgZmlsdGVyKGxhbmc9PXNlbGVjdGVkTGFuZykKICAKICBERnN1YiRkYXRlIDwtIGdzdWIoIlQuKiIsICIiLCBERnN1YiRjcmVhdGVkX2F0KQogIAogIHdvcmRERiA8LSBERnN1YiAlPiUKICAgIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lCiAgICAjIGFudGlfam9pbihmcm9tSlNPTihmaWxlPXBhc3RlMCgic3RvcHdvcmRzLWpzb24tbWFzdGVyL2Rpc3QvIiwgc2VsZWN0ZWRMYW5nLCAiLmpzb24iLCBzZXA9IiIpKSkKICAgIGFudGlfam9pbihzdG9wd29yZHMobGFuZ3VhZ2UgPSBzZWxlY3RlZExhbmcsIHNvdXJjZSA9ICJzdG9wd29yZHMtaXNvIikpCiAgCiAgc2NvcmVERiA8LSB3b3JkREYgJT4lCiAgICBpbm5lcl9qb2luKG15U2VudGltZW50TGV4aWNvbiwgYnk9YygibGFuZyIsICJ3b3JkIikpICU+JQogICAgY291bnQoc3RhdHVzX2lkLCBzZW50aW1lbnQpICU+JQogICAgc3ByZWFkKHNlbnRpbWVudCwgbikKICBzY29yZURGW2lzLm5hKHNjb3JlREYpXSA8LSAwCiAgc2NvcmVERiA8LSBzY29yZURGICU+JQogICAgbXV0YXRlKHNlbnRpbWVudF9zY29yZT0ocG9zaXRpdmUtbmVnYXRpdmUpLyhwb3NpdGl2ZStuZWdhdGl2ZSkpIAogIAogIHR3ZWV0U2NvcmVERiA8LSByaWdodF9qb2luKERGc3ViLCBzY29yZURGLCBieT0ic3RhdHVzX2lkIikKICAKICBpZihsZW5ndGgoa2V5d29yZCkhPTEpewogICAga2V5d29yZCA8LSBwYXN0ZShrZXl3b3JkLCBjb2xsYXBzZT0ifCIpCiAgfQogIGtleXdvcmRERiA8LSB0d2VldFNjb3JlREYgJT4lCiAgICBmaWx0ZXIoZ3JlcGwoa2V5d29yZCwgdGV4dCkpCiAgCiAgcmV0dXJuKAogICAga2V5d29yZERGICU+JQogICAgICBncm91cF9ieShkYXRlKSAlPiUKICAgICAgc3VtbWFyaXplKG92ZXJhbGxfc2VudGltZW50PW1lYW4oc2VudGltZW50X3Njb3JlKSkKICApCn0KYGBgCgoKVmlzdWFsaXphdGlvbgoKYGBge3J9CgojIEZpcnN0bHkgbWFrZSBhIHdvcmQgZnJlcXVlbmN5IHBsb3QKIyBjb25uZWN0IHRvIGRhdGEgYmFzZQpjb25uPWRiQ29ubmVjdChTUUxpdGUoKSxkYnBhdGgpCiMgZ2V0IHR3aXR0ZXIgZGF0YSB3aXRoIGdlbyBpbmZvcm1hdGlvbgp0d2VldHNHZW89Z2V0VHdpdHRlckRhdGEoY29ubixwZXJpb2QgPSBOVUxMKQojIGdldCB0d2l0dGVyIGRhdGEgd2l0aCBnaXZpbmcga2V5d29yZHMgYW5kIHRpbWUKCm1hc2sgPC0gZ2V0VHdpdHRlclRyZW5kKGNvbm4sZ2VvaW5mbyA9IE5VTEwsa2V5d29yZHMgPSBjKCdtYXNrJywnTjk1JykpCm1hc2sgPC0gbXV0YXRlKG1hc2ssCiAgICAgICB4ID1jKDE6MzMpICkKCiMgbWFraW5nIGEgd29yZCBmcmVxdWVudCBwbG90IG9mIG1hc2sgcmVsYXRlZCBkYXRhLgpnZ3Bsb3QoZGF0YSA9IG1hc2ssIGFlcyh4ID0geCwgeSA9IG51bWJlcikpICsKICBnZW9tX2FyZWEoY29sb3I9ImJsdWUiLGZpbGw9InB1cnBsZSIsYWxwaGE9LjIpCgojIG1ha2luZyBhIHNlbnRpbWVudCBzY29yZSBwbG90IG9mIG1hc2sgcmVsYXRlZCBkYXRhLgpnZ3Bsb3QoZGF0YSA9IG1hc2ssIGFlcyh4ID0geCwgeSA9IHNlbnRpbWVudF9zY29yZSkpICsKICBnZW9tX2xpbmUoKSsKICBnZW9tX3BvaW50KHNpemU9NCxzaGFwZT0yMixjb2xvcj0iZGFya3JlZCIsZmlsbD0icGluayIpCiMgZGlzY29ubmVjdCBkYXRhIGJhc2UKZGJEaXNjb25uZWN0KGNvbm4pCgpgYGAKCmBgYHtyfQoKIyBsb2FkIHNwcmVhZCBkYXRhCmRhaWx5IDwtIHJlYWQuY3N2KGZpbGU9ICIvVXNlcnMvbWFjL0Rlc2t0b3AvVHJpbml0eS91c19jb3ZpZDE5X2RhaWx5LmNzdiIsIGhlYWRlcj1UUlVFKQpzcHJlYWRfZGF0YSA8LSBzZWxlY3QoZGFpbHksZGF0ZSxob3NwaXRhbGl6ZWRDdW11bGF0aXZlLGRlYXRoLGRlYXRoSW5jcmVhc2UsbmVnYXRpdmVJbmNyZWFzZSxwb3NpdGl2ZUluY3JlYXNlKSU+JQogIG11dGF0ZShkYXRlX25ldz15bWQoZGF0ZSkpJT4lCiAgYXJyYW5nZShkYWlseSwgZGVzYyhkYXRlX25ldykpCnNwcmVhZF9kYXRhIDwtIHNwcmVhZF9kYXRhWzY4OjEwMCxdCgoKIyB1c2luZyBwbG90bHkgcGFja2FnZSB0byBtYWtlIGEgcGxvdCB0aGF0IGJvdGggaGF2ZSBkZWF0aCwgc2VudGltZW50IGFuZCBmcmVxdWVuY3kKcG9zaXRpdmVpbmNyZWFzZSA8LSBzcHJlYWRfZGF0YVssNl0KZnJlcXVlbmN5IDwtIG1hc2skbnVtYmVyCnNlbnRpbWVudCA8LSBtYXNrJHNlbnRpbWVudF9zY29yZQpkYXRlIDwtc3ByZWFkX2RhdGEkZGF0ZV9uZXcKZGF0YSA8LSBkYXRhLmZyYW1lKGRhdGUsIHBvc2l0aXZlaW5jcmVhc2UsIGZyZXF1ZW5jeSwgc2VudGltZW50KQoKYXkgPC0gbGlzdCgKICB0aWNrZm9udCA9IGxpc3QoY29sb3IgPSAiYmx1ZSIpLAogIG92ZXJsYXlpbmcgPSAieSIsCiAgc2lkZSA9ICJyaWdodCIsCiAgdGl0bGUgPSAidGhlIGZyZXF1ZW5jeSBvZiAiCikKCmZpZyA8LSBwbG90X2x5KGRhdGEsIHggPSB+ZGF0ZSwgeSA9IH5wb3NpdGl2ZWluY3JlYXNlLCBuYW1lID0gJ3Bvc2l0aXZlaW5jcmVhc2UnLCB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnKSAKZm9yKGkgaW4gMTozMikKICBpZigoc2VudGltZW50W2ldK3NlbnRpbWVudFtpKzFdKS8yID4wICl7CiAgICBmaWcgPC1maWcgJT4lIGFkZF90cmFjZSh5ID0gZnJlcXVlbmN5W2k6KGkrMSldLCB4PWRhdGVbaTooaSsxKV0sIG5hbWUgPSBOVUxMLCAgbW9kZSA9ICdsaW5lcycseWF4aXMgPSAieTIiLGNvbG9yID0gSSgiZ3JlZW4iKSkKICB9ZWxzZXsKICAgICAgICAgICAgICAKICAgIGZpZyA8LWZpZyAlPiUgYWRkX3RyYWNlKHkgPSBmcmVxdWVuY3lbaTooaSsxKV0sIHg9ZGF0ZVtpOihpKzEpXSwgbmFtZSA9IE5VTEwsICBtb2RlID0gJ2xpbmVzJyx5YXhpcyA9ICJ5MiIsY29sb3IgPSBJKCJyZWQiKSkgICAgICAgCiAgICB9CiAgICAgICAgICAgICAgCmZpZyA8LSBmaWcgJT4lIGxheW91dCgKICB0aXRsZSA9ICJTdGF0aXN0aWNzIGFib3V0ICdtYXNrLCBOOTUnIiwgeWF4aXMyID0gYXksCiAgeGF4aXMgPSBsaXN0KHRpdGxlPSJ4IikpCmZpZwoKCmBgYAoKIyBHZW9tIHBsb3RzCgpgYGB7cn0KCiMgRm9yIHRoZSBnZW8gcGxvdAojc3RhdGVzIDwtIGMoInRleGFzIiwib2tsYWhvbWEiLCJrYW5zYXMiLCJsb3Vpc2lhbmEiLCJhcmthbnNhcyIsIm1pc3NvdXJpIiwiaW93YSIsCiMid2lzY29uc2luIiwibWljaGlnYW4iLCJpbGxpbm9pcyIsImluZGlhbmEiLCJvaGlvIiwia2VudHVja3kiLCJ0ZW5uZXNzZWUiLAojImFsYWJhbWEiLCJtaXNzaXNzaXBwaSIsImZsb3JpZGEiLCJnZW9yZ2lhIiwic291dGggY2Fyb2xpbmEiLCJub3J0aCBjYXJvbGluYSIsCiMidmlyZ2luaWEiLCJ3ZXN0IHZpcmdpbmlhIiwibWFyeWxhbmQiLCJkZWxhd2FyZSIsInBlbm5zeWx2YW5pYSIsIm5ldyBqZXJzZXkiLAojIm5ldyB5b3JrIiwiY29ubmVjdGljdXQiLCJyaG9kZSBpc2xhbmQiLCJtYXNzYWNodXNldHRzIiwidmVybW9udCIsCiMibmV3IGhhbXBzaGlyZSIsIm1haW5lIikKCiN0dXJuIGRhdGEgZnJvbSB0aGUgbWFwcyBwYWNrYWdlIGluIHRvIGEgZGF0YSBmcmFtZSBzdWl0YWJsZSBmb3IgcGxvdHRpbmcgd2l0aCBnZ3Bsb3QyCiNtYXBfc3RhdGVzIDwtIG1hcF9kYXRhKCJjb3VudHkiLCBzdGF0ZXMpCiMgVG8gZHJhdyB0aGUgYm9yZGVyLWJ5IGdyb3VwIDEwCiNtYXBfc3RhdGVzX2JvcmRlciA8LSBtYXBfZGF0YSgic3RhdGUiLHN0YXRlcykKCgp2aWV3KHR3ZWV0c0dlbykKdHdlZXRzR2VvIDwtIHR3ZWV0c0dlbyAlPiUKICBncm91cF9ieShzdGF0ZSklPiUKICBtdXRhdGUoc3VtID0gbigpLAogICAgICAgICBsb25nPWxuZykKCgp0d2VldHNHZW9fRW4gPC0gZmlsdGVyKHR3ZWV0c0dlbywgY291bnRyeSA9PSAnVW5pdGVkIFN0YXRlcycpClZpZXcodHdlZXRzR2VvX0VuKQojIHN1bW1hcnkodHdlZXRzR2VvX0VuJHN1bSkKIyBkaXZpZGUgc3VtIG9mIHR3ZWV0cyBpbiBlYWNoIHN0YXRlIGludG8gNCBwYXJ0cyB3aGljaCBpcyAKIyB0d2VldHNHZW9fRW4kY3V0IDwtIGN1dCh0d2VldHNHZW9fRW4kc3VtLAojICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcz1jKDAsMTY1OCw3ODkwLDE1MDY0LDE4MjA2KSwKICMgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlLmxvd2VzdCA9IFQpCgp0d2VldHNHZW9fRW5fMSA8LSB0d2VldHNHZW9fRW4lPiUKICBtdXRhdGUobG9uZz1hcy5kb3VibGUobG9uZyksCiAgICAgICAgIGxhdD1hcy5kb3VibGUobGF0KSklPiUKICBncm91cF9ieShzdGF0ZSklPiUKICBzdW1tYXJpemUoc3VtX3N0YXRlcz1uKCksIG1lYW5fc2VudGltZW50PW1lYW4oc2VudGltZW50X3Njb3JlKSkKCgoKIyBNYWtlIHRoZSBnZW8gcGxvdCBpbiBnZ3Bsb3QKI3R3ZWV0c0dlb19wbG90IDwtIGdncGxvdCgpKwojICBnZW9tX3BvbHlnb24odHdlZXRzR2VvX0VuXzEsIG1hcHBpbmc9YWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWNpdHkpLCBmaWxsPWN1dCkrCiAjIGNvbm5lY3RzIHRoZSBvYnNlcnZhdGlvbnMgaW4gdGhlIG9yZGVyIGluIHdoaWNoIHRoZXkgYXBwZWFyIGluIHRoZSAnbWFwX3N0YXRlcycKIyAgZ2VvbV9wYXRoKG1hcF9zdGF0ZXMsIG1hcHBpbmc9YWVzKHg9bG9uZywgeT1sYXQsZ3JvdXA9Y2l0eSksY29sb3I9ImdyZXkiKSsKICMgIEFkZCB0aGUgYm9yZGVyIHRvIG1ha2UgaXQgY2xlYXItYnkgR3JvdXAgMTAKIyAgZ2VvbV9wYXRoKG1hcF9zdGF0ZXNfYm9yZGVyLCBtYXBwaW5nPWFlcyh4PWxvbmcsIHk9bGF0LGdyb3VwPWNpdHkpLGNvbG9yPSJibGFjayIpKwogIAogIyAgZGlzcGxheSBkaXNjcmV0ZSB2YWx1ZXMgb24gYSBtYXAKIyAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iQmx1ZXMiKSsKICAjIGNoYW5nZSB0aGUgbmFtZSBvZiB4LCB5LCBhbmQgdGl0bGUKIyAgeGxhYigiTG9uZ3RpdHVkZSIpK3lsYWIoIkxhdGl0dWRlIikrZ2d0aXRsZSgidHdlZXRzR2VvIikrCiAjICBhZGQgbWFya3MKIyAgbGFicyhmaWxsPSJudW1iZXIgb2YgdHdlZXRzIikrCiMgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemUgPSAxOCkpCgojIHR3ZWV0c0dlb19wbG90CgoKQXByNDAgPC0gcmVhZC5jc3YoZmlsZT0gIi9Vc2Vycy9tYWMvRGVza3RvcC9UcmluaXR5L3VzX3N0YXRlc19kYWlseS5jc3YiLCBoZWFkZXI9VFJVRSkKR2VvcGxvdCA8LSBtZXJnZShBcHI0MCx0d2VldHNHZW9fRW5fMSAsIGJ5PWMoInN0YXRlIikpCkdlb3Bsb3QkaG92ZXIgPC0gd2l0aChHZW9wbG90LCBwYXN0ZShzdGF0ZSwgJzxicj4nLCAgJzxicj4nLCAiUG9zaXRpdmU6IiwgcG9zaXRpdmUsICI8YnI+IiwiZGVhdGg6IiwgZGVhdGgsIjxicj4iLCAibnVtYmVyIG9mIHR3ZWV0cyIsIHN1bV9zdGF0ZXMsJzxicj4nLCAic2VudGltZW50IHNjb3JlIG9mIHRoaXMgc3RhdGUiLCBtZWFuX3NlbnRpbWVudCkpICNwdXQgZGF0YQpmaWcgPC0gcGxvdF9nZW8oR2VvcGxvdCwgbG9jYXRpb25tb2RlID0gJ1VTQS1zdGF0ZXMnKSAKZmlnIDwtIGZpZyAlPiUgYWRkX3RyYWNlKAogIGxvY2F0aW9ucyA9IH5zdGF0ZSwKICB0eXBlPSdjaG9yb3BsZXRoJywKICB6PSB+bWVhbl9zZW50aW1lbnQsCiAgdGV4dCA9IH5ob3ZlciwKICBjb2xvcnM9IlJlZHMiCikKIyBBZGQgYSB0aXRsZQpmaWcgPC0gZmlnICU+JSBsYXlvdXQodGl0bGUgPSAic2VudGltZW50IHNjb3JlIG9mIGVhY2ggc3RhdGUiKQojIEZpbmFsCmZpZwoKYGBgCgpgYGB7cn0KZmlnIDwtIHBsb3RfZ2VvKEdlb3Bsb3QsIGxvY2F0aW9ubW9kZSA9ICdVU0Etc3RhdGVzJykgCmZpZyA8LSBmaWcgJT4lIGFkZF90cmFjZSgKICBsb2NhdGlvbnMgPSB+c3RhdGUsCiAgdHlwZT0nY2hvcm9wbGV0aCcsCiAgej0gfmRlYXRoLAogIHRleHQgPSB+aG92ZXIsCiAgY29sb3JzPSJQdXJwbGVzIgopCiMgQWRkIGEgdGl0bGUKZmlnIDwtIGZpZyAlPiUgbGF5b3V0KHRpdGxlID0gInNlbnRpbWVudCBzY29yZSBvZiBlYWNoIHN0YXRlIikKIyBGaW5hbApmaWcKYGBgCgoKCgoKCg==